package com.holic.spring.custom.resolver;

import com.holic.spring.custom.annotation.Argument;
import com.holic.spring.custom.annotation.Arguments;
import graphql.GraphQLContext;
import graphql.schema.DataFetchingEnvironment;
import graphql.schema.GraphQLArgument;
import graphql.schema.GraphQLFieldDefinition;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @ClassName MethodArgumentResolver
 * @Description
 * @Author Neil
 * @Date 2024/11/18 9:28
 * @Version V1.0
 **/
public class MethodArgumentResolver {

    private static MethodArgumentResolver INSTANCE = new MethodArgumentResolver();

    public static MethodArgumentResolver getInstance(){
        return INSTANCE;
    }

    public Object[] resolve(DataFetchingEnvironment environment, Method method) throws Exception{
        GraphQLFieldDefinition fieldDefinition = environment.getFieldDefinition();
        List<GraphQLArgument> graphQLArguments = fieldDefinition.getArguments();
        Map<String, Object> argumentsMap = environment.getArguments();
        List<Object> params = new ArrayList<>();
        int argumentIdx = 0;
        for (Parameter parameter : method.getParameters()) {
            Argument argument = parameter.getAnnotation(Argument.class);
            Arguments arguments = parameter.getAnnotation(Arguments.class);
            if(argument == null && arguments == null){
                params.add(resolve(environment, parameter));
            }else {
                if (argument != null) {
                    GraphQLArgument graphQLArgument = graphQLArguments.get(argumentIdx);
                    String paramName = getParamName(argument, graphQLArgument);
                    Object value = argumentsMap.get(paramName);
                    if(value instanceof Map){
                        params.add(convertMapToBean((Map<String, Object>)value, parameter.getType()));
                    }else if(value instanceof List) {
                        params.add(convertListToBeanList((List)value, parameter.getParameterizedType()));
                    }else {
                        params.add(value);
                    }
                    argumentIdx++;
                }
                if(arguments != null) {
                    params.add(convertMapToBean((Map<String, Object>)argumentsMap, parameter.getType()));
                }
            }
        }
        return params.toArray(new Object[0]);
    }

    private Object resolve(DataFetchingEnvironment environment, Parameter parameter){
        GraphQLContext context = environment.getGraphQlContext();
        Class clazz = parameter.getType();
        if(clazz.isAssignableFrom(HttpServletRequest.class)){
            return context.get("request");
        }
        if(clazz.isAssignableFrom(HttpServletResponse.class)){
            return context.get("response");
        }
        if(clazz.isAssignableFrom(DataFetchingEnvironment.class)){
            return environment;
        }
        if(clazz.isAssignableFrom(GraphQLContext.class)){
            return context;
        }
        return null;
    }
    /**
     * Get graphQL argument name
     * @param argument
     * @param graphQLArgument
     * @return
     */
    private String getParamName(Argument argument, GraphQLArgument graphQLArgument){
        if(argument == null){
            return null;
        }
        String paramName = argument.value();
        if(StringUtils.isBlank(paramName)){
            paramName = graphQLArgument.getName();
        }
        return paramName;
    }

    /**
     * Convert Map to JavaBean
     * @param mapValue
     * @param clazz
     * @return
     */
    private Object convertMapToBean(Map<String, Object> mapValue, Class<?> clazz){
        BeanWrapper wrapper = new BeanWrapperImpl(clazz);
        wrapper.setAutoGrowNestedPaths(true);
        wrapper.setPropertyValues(mapValue);
        return wrapper.getWrappedInstance();
    }

    /**
     * Convert List to JavaBeanList
     * @param listValue
     * @param type
     * @return
     */
    private Object convertListToBeanList(List<Object> listValue, Type type){
        if(type instanceof ParameterizedType){
            ParameterizedType parameterizedType = (ParameterizedType) type;
            Class<?> clazz = (Class<?>) parameterizedType.getActualTypeArguments()[0];
            return listValue.stream()
                    .map(item -> {
                        if(item instanceof Map){
                            return convertMapToBean((Map<String, Object>)item, clazz);
                        }else {
                            return item;
                        }
                    })
                    .collect(Collectors.toList());
        }else {
            return listValue;
        }
    }

}
